/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "limburstsim.h"
#include "filter.h"



/*************************************************************************
 * Initialize Limit Burst with the average new connections per second
 * and the peak connections per second
 *************************************************************************/
LimitBurstFilter::LimitBurstFilter(){
}

int LimitBurstFilter::InitFilter(InitInterface* fip){
	
	const char* fir	= fip->GetInitParameter("limitburst-msg");
	numberofips		= 0;
	ipcounter		= 0; 
	avg			= atoi((const char*)fip->GetInitParameter("averagepersec")); 
	peak			= atoi((const char*)fip->GetInitParameter("maxburst"));
	
	if((fir)){
		filterRedirect	= (char*)malloc(strlen(fir)+1);
		memcpy(filterRedirect, fir, strlen(fir)+1);
	}
	else
		filterRedirect=0;
	
	numberofticks = (int)round((double)(peak/avg)) + (peak%avg > 0? 1:0);
	return 0;
}
/*************************************************************************
 * Get the filter name
 *************************************************************************/
const char* LimitBurstFilter::GetFilterName(){
	return "Rate-Limiter";
}

/*************************************************************************
 * Get the redirect url
 *************************************************************************/
const char*	LimitBurstFilter::GetFilterRedirect(){
	return filterRedirect;
}

/*************************************************************************
 * Get the filter type - When it is to be called
 *************************************************************************/
int LimitBurstFilter::GetFilterType(){
	return FILTER_START|FILTER_FREE|FILTER_DUMP;
}

/*************************************************************************
 * Destructor
 *************************************************************************/
LimitBurstFilter::~LimitBurstFilter(){
	int j;
	flogit()<<"Limburst Freeing"<<flend;
	for(j=0;j<numberofips ;j++)
		free(ipcounter[j].ticks);
	free(ipcounter);
	ipcounter=0;
	if(filterRedirect)
		free(filterRedirect);
}

/*************************************************************************
 * The actual filter function
 *************************************************************************/
int LimitBurstFilter::DoFilter(const char * ip, FILTER_TYPE type, FilterData* param1){
	
	char tmps128[128];

	int i, j;
	int totalhits;
	int retvalue=0;
	time_t now;

	time_t oldesttime;
	long oldestindex;

	now = time(0);
	safetimecopy(oldesttime, now);
	oldestindex = -1;


	switch(type){

		case FILTER_START:
   			if(ip==0)
				return 0;
			
			for(i = 0; i < numberofips; i++ ){
				if((now - ipcounter[i].lastupdatedtime > numberofticks+1) && ipcounter[i].lastupdatedtime < oldesttime){
					oldesttime = ipcounter[i].lastupdatedtime;
					oldestindex = i;
				}

				if(FilterChain::CompareTwoIPs(ip, (char*)ipcounter[i].ipaddress)){

					int diff = (int)difftime(now, ipcounter[i].lastupdatedtime);

					if(diff<0){
						retvalue=1;
						goto THE_END;
					}
					if( diff != 0){
						if(diff < numberofticks){
							for(j = numberofticks-1 - diff ; j >=0  ; j-- )
								ipcounter[i].ticks[j+diff].hits = ipcounter[i].ticks[j].hits;
						}
						for(j = 1; j < (diff<numberofticks?diff:numberofticks); j++)
							ipcounter[i].ticks[j].hits = 0;

						ipcounter[i].ticks[0].hits=0;
						safetimecopy(ipcounter[i].lastupdatedtime, now);
					}

			
					totalhits=0;
					for(j=0;j<numberofticks;j++){
						totalhits+=ipcounter[i].ticks[j].hits;
					}
			

					if(round(totalhits/numberofticks) >=avg){
						/*log*/snprintf(tmps128, 127, "Limitburst %s over ", ip);
						retvalue=0;			
					}
					else{
						/*log*/snprintf(tmps128, 127,"Limitburst %s under ", ip);
						ipcounter[i].ticks[0].hits++;
						retvalue=1;
					}

					for(j=0;j<numberofticks;j++){
						/*log*/sprintf(tmps128+strlen(tmps128),"%d ", ipcounter[i].ticks[j].hits );
					}
					flogit()<<tmps128<<flend;
			
					goto THE_END;
				}
			}
	
			if(numberofips+1 > MAX_IPTABLE_IPS){

				if(oldestindex!=-1){

					flogit()<<"Limitburst "<< ip <<" replaced with "<< ipcounter[oldestindex].ipaddress <<flend;

					FilterChain::AssignIP((char*)ipcounter[oldestindex].ipaddress, FILTER_ADDRSTRLEN, ip);
					safetimecopy(ipcounter[oldestindex].lastupdatedtime,now);
					ipcounter[oldestindex].ticks[0].hits=1;
			
					for(i=1;i< numberofticks ;i++){
						ipcounter[oldestindex].ticks[i].hits=0;
					}
				}
				else{
					flogerr()<<"Limburst auto pass"<<flend;
				}
				retvalue=1;
			}
			else{
					flogit()<<"Limitburst new "<<ip<<" "<<numberofips<<flend;
					if(limitburstRealloc()!=0){
						FilterChain::AssignIP((char*)ipcounter[numberofips].ipaddress, FILTER_ADDRSTRLEN, ip);
						now=time(0);
						safetimecopy(ipcounter[numberofips].lastupdatedtime, now);
						ipcounter[numberofips].ticks[0].hits=1;
						for(i=1;i< numberofticks ;i++){
							ipcounter[numberofips].ticks[i].hits=0;
						}

						numberofips++;
					}
					retvalue=1;

			}
			break;

		case FILTER_FREE:
			/*Not used anymore*/
			retvalue=1;
			break;

		case FILTER_DUMP:
			retvalue=1;
			break;

		default:
			flogit()<<"Limitburst unknown filter command"<<flend;
			retvalue=1;
			break;
	}

THE_END:
	return retvalue;

}

/*************************************************************************
 * LimBurst Realloc for IPs
 *************************************************************************/
int LimitBurstFilter::limitburstRealloc(){
	IPCounter* tempp;	
	if((tempp = (IPCounter*)realloc(ipcounter, (numberofips+1)*sizeof(IPCounter))) == 0){
		flogit()<<"Limitburst realloc failed"<<flend;

		return 0;		
	}
	else{
		ipcounter = tempp;
		if((ipcounter[numberofips].ticks = (TickMark*)malloc(sizeof(TickMark)*numberofticks))==0){
			/*log*/flogit()<<"Limitburst alloc (tickmark) failed"<<flend;
			return 0;
			
		}
	}	
	return 1;
}
